package main

import (
	"bufio"
	"crypto/md5"
	"encoding/json"
	"flag"
	"fmt"
	"gogo"
	"io"
	"io/ioutil"
	"log"
	"net/http"
	"os"
	"regexp"
	"strconv"
	"strings"
	"time"
)

var flagDBFile string
var flagPort string
var flagBuild bool
var flagImport string

func init() {
	flag.StringVar(&flagDBFile, "dbfile", "zi.db", "file path for db")
	flag.StringVar(&flagPort, "port", "8081", "port")
	flag.BoolVar(&flagBuild, "build", false, "build web index.html")
	flag.StringVar(&flagImport, "import", "", "import sql")
	flag.Parse()
}

type gores struct {
	Code int              `json:"code"`
	Msg  string           `json:"msg"`
	Data map[string][]int `json:"data"`
}

type goreq struct {
	X   int    `json:"x"`
	Y   int    `json:"y"`
	Uid string `json:"uid"`
}

func compactZi(a []gogo.Gozi) map[string][]int {
	m := map[string][]int{}
	for _, z := range a {
		k := fmt.Sprintf("%x", z.Uid)
		m[k] = append(m[k], z.X, z.Y)
	}
	return m
}
func makeRes(code int, msg string, data []gogo.Gozi) []byte {
	b, err := json.Marshal(gores{code, msg, compactZi(data)})
	if err != nil {
		log.Fatal(err)
	}
	return b
}
func writeResErr(w http.ResponseWriter, msg string) {
	_, err := fmt.Fprintf(w, "%s", makeRes(-1, msg, []gogo.Gozi{}))
	if err != nil {
		log.Fatal(err)
	}
}
func writeRes(w http.ResponseWriter, data []gogo.Gozi) {
	_, err := fmt.Fprintf(w, "%s", makeRes(0, "OK", data))
	if err != nil {
		log.Fatal(err)
	}
}

var reSrc = regexp.MustCompile(`<script src="([^"]+)">`)

func md5public(fname string) []byte {
	f, err := os.Open("public/" + fname)
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	h := md5.New()
	if _, err = io.Copy(h, f); err != nil {
		log.Fatal(err)
	}
	return h.Sum(nil)
}
func buildHtml() {
	// index.html --> public/index.html
	f, err := os.Open("index.html")
	if err != nil {
		log.Fatal(err)
	}
	defer f.Close()
	sc := bufio.NewScanner(f)
	a := []string{}
	for sc.Scan() {
		line := sc.Bytes()
		ls := strings.TrimLeft(string(line), " ")
		if reSrc.Match(line) {
			m := reSrc.FindSubmatch(line)
			fsrc := string(m[1])
			ls = fmt.Sprintf(`<script src="%s?v=%x"></script>`,
				fsrc, md5public(fsrc))
			log.Printf("%s\n", fsrc)
		}
		if len(ls) > 0 {
			a = append(a, ls)
		}
	}
	if sc.Err() == bufio.ErrTooLong {
		log.Fatal(err)
	}
	c := strings.Join(a, "\r\n")
	err = ioutil.WriteFile("public/index.html", []byte(c), 0644)
	if err != nil {
		log.Fatal(err)
	}
}

func timeIt(name string, f func(w http.ResponseWriter, r *http.Request)) func(w http.ResponseWriter, r *http.Request) {
	return func(w http.ResponseWriter, r *http.Request) {
		t := time.Now()
		f(w, r)
		d := time.Now().Sub(t)
		log.Printf("%s %s\n", name, d.String())
	}
}

func main() {
	log.SetFlags(log.Lshortfile | log.LstdFlags)

	if flagBuild {
		buildHtml()
	}

	f := gogo.Init(true, "gogo.j")
	defer f.Close()

	if flagImport != "" {
		gogo.LoadFromSql(flagImport)
	}

	// just for test
	http.Handle("/", http.FileServer(http.Dir("public")))

	// get
	http.HandleFunc("/getall", timeIt("/getall", func(w http.ResponseWriter, r *http.Request) {
		err := r.ParseForm()
		if err != nil {
			writeResErr(w, err.Error())
			return
		}
		f := r.Form
		if !f.Has("x1") {
			writeResErr(w, "no x1")
			return
		}
		x1, err := strconv.Atoi(f.Get("x1"))
		if err != nil {
			writeResErr(w, "x1 not int")
			return
		}
		if !f.Has("x2") {
			writeResErr(w, "no x2")
			return
		}
		x2, err := strconv.Atoi(f.Get("x2"))
		if err != nil {
			writeResErr(w, "x2 should be int")
			return
		}
		if !f.Has("y1") {
			writeResErr(w, "no y1")
			return
		}
		y1, err := strconv.Atoi(f.Get("y1"))
		if err != nil {
			writeResErr(w, "y1 should be int")
			return
		}
		if !f.Has("y2") {
			writeResErr(w, "no y2")
			return
		}
		y2, err := strconv.Atoi(f.Get("y2"))
		if err != nil {
			writeResErr(w, "y2 should be int")
			return
		}
		if !(x1 < x2) {
			writeResErr(w, "x1<x2")
			return
		}
		if !(y1 < y2) {
			writeResErr(w, "y1<y2")
			return
		}
		a := gogo.GetRect(x1, y1, x2, y2)
		writeRes(w, a)
	}))
	http.HandleFunc("/doit", timeIt("/doit", func(w http.ResponseWriter, r *http.Request) {
		log.Printf("/doit %v\n", r.RemoteAddr)
		err := r.ParseForm()
		if err != nil {
			writeResErr(w, err.Error())
			return
		}
		formData := goreq{}
		err = json.NewDecoder(r.Body).Decode(&formData)
		if err != nil {
			writeResErr(w, err.Error())
			return
		}
		log.Printf("%v\n", formData)
		uid, err := strconv.ParseInt(formData.Uid, 16, 64)
		err = gogo.Insert(formData.X, formData.Y, int(uid))
		if err != nil {
			log.Printf("%s\n", err)
			writeResErr(w, err.Error())
			return
		}
		// gogo.DebugState()
		// a := getAll(db, -1000, 1000, -1000, 1000)
		writeRes(w, []gogo.Gozi{})
	}))
	http.HandleFunc("/debug", timeIt("/debug", func(w http.ResponseWriter, r *http.Request) {
		j, err := json.Marshal(gogo.GetDebugInfo())
		if err != nil {
			log.Fatal(err)
		}
		fmt.Fprintf(w, "%s", j)
	}))

	log.Println("listen on ", flagPort)
	log.Fatal(http.ListenAndServe(":"+flagPort, nil))
}
